﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Caching;
using System.Web.Script.Serialization;
using System.Web.UI.WebControls;
using System.Xml.Linq;
using DVK.SP2007;
using DVK.SP2007.Constants;
using DVK.SP2007.Resources;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebControls;

namespace DVK.SP2007.Pages
{
    /// <summary>
    /// MapMetadata pages allows the user to create mapping configurations.
    /// </summary>
    public partial class MapMetadata : LayoutsPageBase
    {
        SPWeb _web;
        private const string SessionCurrentDvkMappings = "SessionCurrentDvkMappings";

        /// <summary>
        /// Handles the Load event of the Page control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void Page_Load(object sender, EventArgs e)
        {
            _web = SPContext.Current.Web;
            if (!this.IsPostBack)
            {
                //Load all configurations
                LoadConfigurations();
                LoadDvkFields();
                EmptyForm();
            }
        }

        #region Events
        /// <summary>
        /// Handles the Click event of the btnDeleteConfiguration control.
        /// <remarks>
        /// Contains the logic to delete the configuration from metadata mappings list.
        /// </remarks>
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void btnDeleteConfiguration_Click(object sender, EventArgs e)
        {
            if (this.ddlExistingConfigurations.SelectedValue == "-1")
            {
                ShowErrorText(DvkWeb.MapMetadata_ErrorCannotDeleteConfigNew);
                return;
            }

            SPList metadataMappingList = _web.GetListFromUrl(Constants.Urls.ListMetadataMapping);
            SPListItem item = metadataMappingList.GetItemById(int.Parse(this.ddlExistingConfigurations.SelectedValue));
            item.Delete();

            //Reload configurations list
            this.LoadConfigurations();
            this.EmptyForm();
        }

        /// <summary>
        /// Handles the SelectedIndexChanged event of the ddlExistingConfigurations control.
        /// </summary>
        /// <remarks>
        /// Loads new configuration to the Metadata mapping form.
        /// </remarks>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void ddlExistingConfigurations_SelectedIndexChanged(object sender, EventArgs e)
        {
            //Load configuration data - currently only site level.
            if (this.ddlExistingConfigurations.SelectedValue == "-1")
            {
                EmptyForm();
                return;
            }

            int configurationItemId = int.Parse(this.ddlExistingConfigurations.SelectedValue);
            
            SPListItem configurationItem = 
                new MetadataMappingRepository(_web).GetMetadataMappingItem(configurationItemId);

            //Currently only site configuration loading
            this.txtConfigurationTitle.Text = configurationItem[Constants.Fields.Title.Name] as string;
            string mappings = configurationItem[Constants.Fields.DvkMetadataMapping.Name] as string;
            string applyingTo = configurationItem[Constants.Fields.DvkMappingApplyingTo.Name] as string;
            if (!string.IsNullOrEmpty(applyingTo))
            {
                this.ddlMappingApplyingTo.SelectedIndex = -1;
                this.ddlMappingApplyingTo.Items.FindByValue(applyingTo).Selected = true;
            }
            var mappingsList = new JavaScriptSerializer().Deserialize<List<DvkMapping>>(mappings);
            SetMappingsList(mappingsList);
        }
        /// <summary>
        /// Handles the SelectedIndexChanged event of the ddlMappingLevel control.
        /// <remarks>
        /// Only Web mapping level is currently supported.
        /// </remarks>
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void ddlMappingLevel_SelectedIndexChanged(object sender, EventArgs e)
        {
            //TODO: Partial implementation, only supporting Site mappinglevel (Taavi, Date)
            var mappingLevel = (Fields.DvkMappingLevel.DvkMappingLevelChoices)Enum.Parse(typeof(Fields.DvkMappingLevel.DvkMappingLevelChoices), this.ddlMappingLevel.SelectedValue);

            switch (mappingLevel)
            {
                case Fields.DvkMappingLevel.DvkMappingLevelChoices.Web:
                    {
                        //Load all fields
                        LoadAllSPFields();
                        break;
                    }
                case Fields.DvkMappingLevel.DvkMappingLevelChoices.List:
                    {
                        //Load all lists that are not DVK interface related or hidden
                        break;
                    }
                case Fields.DvkMappingLevel.DvkMappingLevelChoices.ContentType:
                    {
                        //Load all contenttypes that are not DVK interface related or hidden
                        break;
                    }
                default:
                    break;
            }
        }

        /// <summary>
        /// Handles the Command event of the btnDeleteMapping control.
        /// </summary>
        /// <remarks>
        /// Deletes the mapping from current configuration listview, but changes take effect in the list when they are saved.
        /// </remarks>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Web.UI.WebControls.CommandEventArgs"/> instance containing the event data.</param>
        protected void btnDeleteMapping_Command(object sender, CommandEventArgs e)
        {
            var mappingsList = GetMappingList();

            //nothing to delete
            if (mappingsList == null || mappingsList.Count == 0)
                return;

            //The command argument pattern is:
            //"SPFieldName;ElementName;Namespace"
            string[] deletionData = e.CommandArgument.ToString().Split(';');

            string spMetadataInternalName = deletionData[0];
            string dvkMetadata = deletionData[1];
            string dvkNamespace = deletionData[2];

            foreach (var mapping in mappingsList)
            {
                if (mapping.ElementName == dvkMetadata &&
                    mapping.Namespace == dvkNamespace &&
                    mapping.SPFieldName == spMetadataInternalName)
                {
                    //Delete the matching item
                    mappingsList.Remove(mapping);
                    break;
                }
            }

            this.SetMappingsList(mappingsList);
        }

        /// <summary>
        /// Handles the Click event of the btnAddMapping control.
        /// <remarks>
        /// Adds new mapping to the listview, but changes are saved to the list, when the configuration is saved.
        /// </remarks>
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void btnAddMapping_Click(object sender, EventArgs e)
        {
            var mappingsList = this.GetMappingList();

            string spMetadataInternalName = this.ddlSPFields.SelectedValue;
            string dvkMetadata = this.ddlDvkFields.SelectedValue;
            XName dvkElementName = XName.Get(dvkMetadata.Split(';').First());
            XName dvkParentName = XName.Get(dvkMetadata.Split(';').Last());
            var mapping = new DvkMapping()
            {
                 SPFieldName = spMetadataInternalName,
                 ParentElementName = dvkParentName.LocalName,
                 ParentNamespace = dvkParentName.NamespaceName,
                 ElementName = dvkElementName.LocalName,
                 Namespace = dvkElementName.NamespaceName
            };

            //Check if already such a mapping is in the listview
            foreach (var item in mappingsList)
            {
                if (item.SPFieldName == mapping.SPFieldName 
                    && item.Namespace == mapping.Namespace 
                    && item.ElementName == mapping.ElementName)
                {
                    ShowErrorText(DvkWeb.MapMetadata_ErrorCannotAddSameMappingTwice);
                    return;
                }
            }

            mappingsList.Add(mapping);

            this.SetMappingsList(mappingsList);
        }
        /// <summary>
        /// Handles the Click event of the btnSaveConfiguration control.
        /// <remarks>
        /// Saves the configuration changes or creates a new configuration item to the metadata mapping list.
        /// </remarks>
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void btnSaveConfiguration_Click(object sender, EventArgs e)
        {
            var mappingList = this.GetMappingList();

            //Validation for saving missing
            if (mappingList.Count == 0)
            {
                ShowErrorText(DvkWeb.MapMetadata_ErrorAtLeastOneMappingIsRequiredForSaving);
                return;
            }
            SPListItem configurationItem = null;
            int configurationId = -1; //-1 means new item

            try
            {
                SPList list = _web.GetListFromUrl(Constants.Urls.ListMetadataMapping);
                configurationId = int.Parse(this.ddlExistingConfigurations.SelectedValue);

                if (configurationId == -1)
                {
                    //Check that there isent item with the same level, applying to and identifier.
                    var mappingLevel = 
                        (Fields.DvkMappingLevel.DvkMappingLevelChoices)Enum.Parse(typeof(Fields.DvkMappingLevel.DvkMappingLevelChoices), this.ddlMappingLevel.SelectedValue, true);
                    var applyingTo =
                        (Fields.DvkMappingApplyingTo.DvkMappingApplyingToChoices)Enum.Parse(typeof(Fields.DvkMappingApplyingTo.DvkMappingApplyingToChoices), this.ddlMappingApplyingTo.SelectedValue, true);

                    //Looks up matching listitem from metadata mapping list
                    //Can be optimized by returning items with only Title field
                    var items = 
                        new MetadataMappingRepository(_web)
                            .GetUniqueMetadataMappingItem(mappingLevel, null, applyingTo);
                    if (items.Count > 0)
                    {
                        string title = items[0][Fields.Title.Name] as string;
                        ShowErrorText(
                                string.Format(
                                        DvkWeb.MapMetadata_DeleteOrModifyExistingItem, 
                                        title
                                    )
                                );
                        return;
                    }
                    
                    configurationItem = list.Items.Add();
                }
                else
                {
                    configurationItem = list.GetItemById(configurationId);
                }

                //Fill data and update
                configurationItem[Constants.Fields.Title.Name] = this.txtConfigurationTitle.Text;
                configurationItem[Constants.Fields.DvkMappingLevel.Name] = this.ddlMappingLevel.SelectedValue;
                configurationItem[Constants.Fields.DvkMetadataMapping.Name] = new JavaScriptSerializer().Serialize(mappingList);
                configurationItem[Constants.Fields.DvkMappingApplyingTo.Name] = this.ddlMappingApplyingTo.SelectedValue;
                //Currently missing identifier, only site level.
                configurationItem.UpdateOverwriteVersion();
            }
            catch (Exception exc)
            {
                //Delete the item, saving failed. We dont want to delete items that existed previously and are being modified.
                if (configurationItem != null && configurationId == -1)
                    configurationItem.Delete();

                ShowErrorText(string.Format(DvkWeb.MapMetadata_ErrorOccuredOnSaving, exc.ToString()));
                return;
            }

            SPUtility.Redirect("settings.aspx", SPRedirectFlags.RelativeToLayoutsPage, HttpContext.Current);
        }

        #endregion

        /// <summary>
        /// Loads the DVK fields list from DvkSettings.
        /// <remarks>
        /// Caches the fields list for 1 hour.
        /// </remarks>
        /// </summary>
        private void LoadDvkFields()
        {
            var dvkFieldsForMappings = Cache[Constants.Caching.DvkFieldsForMappingList] as List<DvkElement>;

            //get the items, if not cached
            if (dvkFieldsForMappings == null)
            {
                DvkSettingsRepository settingsManager = new DvkSettingsRepository(_web);
                string setting = settingsManager.QuerySetting(Constants.Settings.DvkFieldsForMapping);
                if (setting == null)
                {
                    ShowErrorText(DvkWeb.MapMetadata_ErrorDvkFieldsMissingFromSettings);
                    return;
                }

                JavaScriptSerializer serializer = new JavaScriptSerializer();
                try
                {
                    dvkFieldsForMappings = serializer.Deserialize<List<DvkElement>>(setting);
                }
                //If its invalid setting
                catch //(ArgumentException exc)
                {
                    ShowErrorText(DvkWeb.MapMetadata_ErrorDvkFieldsConfigurationIsInvalidInSettings);
                    return;
                }

                Cache.Add(Constants.Caching.DvkFieldsForMappingList, dvkFieldsForMappings, null, DateTime.Now.AddMinutes(60), TimeSpan.Zero, CacheItemPriority.Default, null);
            }

            //fill the dropdown list
            this.ddlDvkFields.DataSource = dvkFieldsForMappings;
            this.ddlDvkFields.DataBind();
        }

        /// <summary>
        /// Shows the error text in lbErrorMessage.
        /// </summary>
        /// <param name="text">The text.</param>
        private void ShowErrorText(string text)
        {
            //TODO: Later find the text based on key and additional parameters 
            lbErrorMessage.Text = text;
        }

        /// <summary>
        /// Empties the form.
        /// </summary>
        private void EmptyForm()
        {
            this.txtConfigurationTitle.Text = "";

            //Load mapping levels and select default 
            var mappingLevelNames = Enum.GetNames(typeof(Fields.DvkMappingLevel.DvkMappingLevelChoices));
            this.ddlMappingLevel.Items.Clear();
            foreach (var mappingLevel in mappingLevelNames)
            {
                //TODO: Get translated enum values!
                this.ddlMappingLevel.Items.Add(new ListItem(mappingLevel));
            }

            //Clear identifiers list
            this.ddlIdentifiers.Items.Clear();

            this.LoadAllSPFields();

            //Recreate applyingto list values
            var mappingApplyingTo = Enum.GetNames(typeof(Fields.DvkMappingApplyingTo.DvkMappingApplyingToChoices));
            this.ddlMappingApplyingTo.Items.Clear();
            foreach (var applyingTo in mappingApplyingTo)
            {
                //TODO: Get translated enum values!
                this.ddlMappingApplyingTo.Items.Add(new ListItem(applyingTo));
            }

            this.SetMappingsList(new List<DvkMapping>());
        }

        /// <summary>
        /// Loads all SharePoint fields with the matching group name sorted by title field ascending .
        /// <remarks>
        /// Groups can be configured through the DvkSettings list using the key 'SPFieldGroupFilter'.
        /// The value is a list separated by ';' as text.
        /// 
        /// SPDVK group fields are ignored (builtin).
        /// 
        /// The result (List of SPField) is cahced for 60 minutes. 
        /// </remarks>
        /// </summary>
        private void LoadAllSPFields()
        {
            //Clear sharepoint fields list
            //Load all fields - once
            var fieldsList = Cache[Constants.Caching.WebSPFieldsForMappingList] as List<SPField>;
            if (fieldsList == null)
            {
                SPFieldCollection fields = _web.Fields;
                fieldsList = new List<SPField>();

                string filter = new DvkSettingsRepository(_web).QuerySetting(Constants.Settings.SPFieldGroupFilter);

                //QuerySetting returns null, if the setting cannot be found
                if (filter == null)
                {
                    //So no filter has been set
                    filter = string.Empty;
                }

                string[] filterArray = filter.Split(';');

                foreach (SPField item in fields)
                {
                    if (item.Group != "SPDVK" && (string.IsNullOrEmpty(filter) || filterArray.Contains(item.Group)))
                    {
                        fieldsList.Add(item);
                    }
                }

                fieldsList.Sort((f1, f2) => f1.Title.CompareTo(f2.Title));

                Cache.Add(Constants.Caching.WebSPFieldsForMappingList, fieldsList, null, DateTime.Now.AddMinutes(60), TimeSpan.Zero, CacheItemPriority.Default, null);
            }

            this.ddlSPFields.DataSource = fieldsList;
            this.ddlSPFields.DataBind();
        }

        /// <summary>
        /// Sets the mappings list to session and binds to listMappings listview.
        /// </summary>
        /// <param name="list">The list.</param>
        private void SetMappingsList(List<DvkMapping> list)
        {
            this.listMappings.DataSource = list;
            this.listMappings.DataBind();
            Session[SessionCurrentDvkMappings] = list;
        }

        /// <summary>
        /// Gets the mapping list from session.
        /// </summary>
        /// <returns></returns>
        private List<DvkMapping> GetMappingList()
        {
            return Session[SessionCurrentDvkMappings] as List<DvkMapping>;
        }

        /// <summary>
        /// Loads the configurations from metadata mapping list.
        /// </summary>
        private void LoadConfigurations()
        {
            //Clear configurations
            this.ddlExistingConfigurations.Items.Clear();

            SPList metadataMappingsList = _web.GetListFromUrl(Constants.Urls.ListMetadataMapping);
            SPQuery query = new SPQuery();
            query.RowLimit = 100;
            query.ViewFields = "<FieldRef Name='Title'/>";

            //Default configuration is inserting a new one.
            this.ddlExistingConfigurations.Items.Add(
                    new ListItem()
                    {
                        Text = DvkWeb.MapMetadata_NewConfigurationText,
                        Value = "-1",
                        Selected = true
                    }
                );

            SPListItemCollection mappingConfigurations = metadataMappingsList.GetItems(query);
            foreach (SPListItem item in mappingConfigurations)
            {
                this.ddlExistingConfigurations.Items.Add(
                        new ListItem(item[Constants.Fields.Title.Name].ToString(), item.ID.ToString())
                    );
            }

        }
    }
}